/*----------------------------------------------------------------------------
Owner(s): Shane Whitfield
$Copyright: $
$Id: $
----------------------------------------------------------------------------*/

#include "go.h"
#include "world.h"
#include "gomgr.h"
#include "render.h"
#include "timermgr.h"
#include "mathutils.h"
#include "ui_elements.h"
#include "player.h"
#include "soundmgr.h"
#include "touchmgr.h"

const float CONSTUCTION_ALPHA_START = 0.3f;
const float CONSTUCTION_ALPHA_END = 0.8f;

/*--------------------------------------------------------------------------*/
CGo::CGo()
{
	m_state = EState_Alive;
	m_maxHealth = 100.0f;
	m_curHealth = m_maxHealth;
	SetPos(Vector2f::Zero);
	m_isVisible = false;
	m_model = new CModel(this);
	m_healthProgress = new CProgressBar();
	m_worldState = EWorldState_In;
    
    SetDepthLayer(EDepthLayer_GoPlayer);
}

bool CGo::IsPointWithin(const Vector2f& i_pos)
{ 
	return m_model->IsPointWithin(i_pos);
}

void CGo::Render()
{
	//is this visible?
	if (!m_isVisible)
	{
		return;
	}
	
	
	m_model->Render();
	
	m_healthProgress->Render();
}

void CGo::Damage(float i_damageValue)
{
#if DEBUG
	if (g_player.IsInGodMode())
	{
		return;
	}
#endif 

	m_curHealth = BBMAX(m_curHealth - i_damageValue, 0.0f);
		
	//if the entity isn't already dead and it's out of health,
	// then make it dead
	if( !IsDead() && m_curHealth <= 0 )
	{
		setState(EState_Dead);
	}
}

void CGo::Update()
{
	//check if visible or not
	m_isVisible = true;
	
	m_model->Update();
	
	float healthPercent = m_curHealth / m_maxHealth;
	m_healthProgress->SetProgress(healthPercent);
	m_healthProgress->SetIsVisible(!IsNear(healthPercent, 1.0f));
	
	float size = GetBoundingRadius() * 1.8;
	m_healthProgress->SetWidth(1.0f);
	m_healthProgress->SetHeight(0.1);
	m_healthProgress->SetScale(size);
	m_healthProgress->SetPos(Vector2f(GetPos().x, GetPos().y - GetBoundingRadius()));
	
	if (doesHandlesTouch() && g_touchmgr.HasContact())
	{
		bool isContactOnGo = false;
		Vector2f worldTouchPos;
		if (g_touchmgr.HasTouchEvent(ETouchType_SingleTouchStart, false))
		{
			worldTouchPos = g_touchmgr.GetTouchEventWorldPos(ETouchType_SingleTouchStart);

			if (IsPointWithin(worldTouchPos))
			{
				isContactOnGo = true;
				if (g_gomgr.IsTouchOfDeath())
				{
					g_touchmgr.ConsumeTouchEvent(ETouchType_SingleTouchStart);
					 RemoveFromWorld();
				}
				else
				{
					bool consumeInput = false;
					on_TouchFocusGained(worldTouchPos, consumeInput);
					if (consumeInput)
					{
						g_touchmgr.ConsumeTouchEvent(ETouchType_SingleTouchStart);
					}
				}
			}
		}
		else if (g_touchmgr.HasTouchEvent(ETouchType_SingleTouchMoved, false))
		{
			worldTouchPos = g_touchmgr.GetTouchEventWorldPos(ETouchType_SingleTouchMoved);
			
			//tell the object the touch focus changed
			if (IsPlacing() || IsPointWithin(worldTouchPos))
			{
				isContactOnGo = true;
				bool consumeInput = false;
				g_touchmgr.ConsumeTouchEvent(ETouchType_SingleTouchMoved);
				on_TouchFocusMoved(worldTouchPos, consumeInput);
				if (consumeInput)
				{
					g_touchmgr.ConsumeTouchEvent(ETouchType_SingleTouchMoved);
				}
			}
		}
		else if (g_touchmgr.HasTouchEvent(ETouchType_SingleTouchEnd, false))
		{
			worldTouchPos = g_touchmgr.GetTouchEventWorldPos(ETouchType_SingleTouchEnd);
			isContactOnGo = true;
			
			//tell the object it lost touch focus
			if (IsPlacing() || IsPointWithin(worldTouchPos))
			{
				bool consumeInput = false;
				g_touchmgr.ConsumeTouchEvent(ETouchType_SingleTouchEnd);
				on_TouchFocusLost(worldTouchPos, consumeInput);
				if (consumeInput)
				{
					g_touchmgr.ConsumeTouchEvent(ETouchType_SingleTouchEnd);
				}
			}
		}
	}
}

void CGo::Heal(float i_healValue)
{
	m_curHealth = BBMIN(m_curHealth + i_healValue, m_maxHealth);
}

void CGo::setState(EState i_state)
{
	m_state = i_state;

	switch(m_state)
	{
	case EState_Dead:
		{
			RemoveFromWorld();
			onDeath();
		}
		break;
	}
}

void CGo::SetPos(const Vector2f& i_newPos)
{
	m_worldMat.SetPos(i_newPos);
}



// convert enum into actual depths
void CGo::calcActualDepthFromDepthEnum()
{
    m_worldMat.SetDepth(g_gomgr.GetDepthFromDepthEnum(m_depthLayer));
}


/*--------------------------------------------------------------------------*/
const CTexture* CGoNode::m_conntectionTexture = NULL;

CGoNode::CGoNode()
{
	m_constructionTime = 2.5f;
	m_tState = 0.0f;
	m_state = EState_Placing;
	setProvidesConnection(true);
	setRequiresConnection(true);
	setConnectionsAllowed(1);
	m_connectionRadius = 0.9f;
	m_isOrphanedFromRoot = true;
	m_hasNodeVisited = false;
	
	SetAlpha(CONSTUCTION_ALPHA_START);
	
	//load up the connection texture if it's not already loaded
	if (!m_conntectionTexture)
	{
		m_conntectionTexture = g_texturemgr.CreateTexture("vein.png");
	}
    
    SetDepthLayer(EDepthLayer_GoPlayer);
}

void CGoNode::Spawn(const Vector2f& i_pos)
{
	super::Spawn(i_pos);
	setState(EState_Constructing);
	setState(EState_NotConnected);
}

void CGoNode::on_TouchFocusGained(const Vector2f& i_touchPos, bool& o_usedInputs)
{
	switch(m_state)
	{
	case EState_Placing :
		{
			//update the nodes pos
			SetPos(i_touchPos);
			o_usedInputs = true;
		}
		break;
	}
}

void CGoNode::on_TouchFocusMoved(const Vector2f& i_touchPos, bool& o_usedInput)
{
	switch(m_state)
	{
	case EState_Placing :
		{
			//update the nodes pos
			SetPos(i_touchPos);
			o_usedInput = true;
		}
		break;
	}
}

void CGoNode::on_TouchFocusLost(const Vector2f& i_touchPos, bool& o_usedInput)
{
	//if valid... place it at this location.. otherwise or remove it
	switch(m_state)
	{
	case EState_Placing :
		{
			//check is space is to place
			
			//place it here
			SetPos(i_touchPos);
			
			setState(EState_Constructing);
			
			o_usedInput = true;
		}
		break;
	}
}

void CGoNode::RemoveFromWorld()
{
	super::RemoveFromWorld();
	
	//unregister node
	removingFromNodeList();
}

void CGoNode::Render()
{  
    super::Render();

    // explicitly render nodes below this current go. 
    Transform2D::glPushMatrix();
    float depth = g_gomgr.GetDepthFromDepthEnum(EDepthLayer_Vein);
    Transform2D::glTranslatef(0.0f, 0.0f, depth);
	
    //render a line between me and the connecting node
    g_texturemgr.EnableTextures(true);
    m_conntectionTexture->Apply();
    
    //render the connections if there is any
	for(int iConnection = 0; iConnection < m_connections.size(); ++iConnection)
	{
		BasicDrawObjects::DrawTexturedLine(GetPos(), m_connections[iConnection]->GetPos(), 0.1f, Color4f());
	}
    
    g_texturemgr.EnableTextures(false);
    Transform2D::glPopMatrix();

    // draw the connection radius while placing
    if (m_state == EState_Placing)
    {
        Color4f yellow(0.2, 1.0f, 0.1, 1.0f);
        BasicDrawObjects::DrawCircle(GetPos(), GetConnectionRadius(), yellow);
    }	
}

void CGoNode::Update()
{
	super::Update();
	
	for(;;)
	{
		//update the connection state
		EState prevState = m_state;
		switch(m_state)
		{
		case EState_Connected:
			{
				if (m_isOrphanedFromRoot)
				{
					setState(EState_NotConnected);
				}
				else
				{
					SetColor(Color4f());
				}
				
			}
			break;
		case EState_NotConnected:
			{
				if (!m_isOrphanedFromRoot)
				{
					setState(EState_Connected);
				}
				else
				{
					SetColor(Color4f(1.0f, 0.0f, 0.0f));
				}
			}
			break;
		case EState_Constructing:
			{
				//if we finished the construction
				// $$$ for now base this on time
				if ((T() - m_tState) >= m_constructionTime)
				{
					setState(EState_NotConnected);
				}
				else
				{
					float percentComplete = (T() - m_tState) / m_constructionTime;
					float newAlpha = ((CONSTUCTION_ALPHA_END - CONSTUCTION_ALPHA_START) * percentComplete) + CONSTUCTION_ALPHA_START;
					
					//update the alpha
					SetAlpha(newAlpha);
				}
			}
			break;
		case EState_Placing:
			{
				calculateConnections();
			}
			break;
		}
		
		if (prevState == m_state)
		{
			break;
		}
	}
	
	//set the orphan node to true has default and hope the nodemgr picks us up
	m_isOrphanedFromRoot = true;
	m_hasNodeVisited = false;
}

struct ConnectionsToDrop
{
			ConnectionsToDrop() {
				Dist = 0.0f; NodeToDrop = NULL; }
				
	float		Dist;
	CGoNode*	NodeToDrop;
};

void CGoNode::calculateConnections()
{
	m_connections.clear();
	g_gomgr.GetNearConnectionProviderNodes(GetPos(), GetConnectionRadius(), m_connections, this);
	
	//check if we need to drop connections
	int countToDrop = m_connections.size() - m_connectionsAllowed;
	if (countToDrop > 0)
	{
		std::vector<ConnectionsToDrop> connetionsToDrop;
		connetionsToDrop.resize(countToDrop);
		
		//if we retreived more than our connections allow, remove the ones farthest away
		for(int iConnection = 0; iConnection < m_connections.size(); ++iConnection)
		{
			float dist = Dist(m_connections[iConnection]->GetPos(), GetPos());
			
			//can this connection replace one that is being dropped?
			for(int iToDrop = 0; iToDrop < connetionsToDrop.size(); ++iToDrop)
			{
				if (connetionsToDrop[iToDrop].Dist < dist)
				{
					connetionsToDrop[iToDrop].Dist = dist;
					connetionsToDrop[iToDrop].NodeToDrop = m_connections[iConnection];
					break;
				}
			}
		}
		
		//go through all the dropped connections and remove them from the connections list
		for(int iToDrop = 0; iToDrop < connetionsToDrop.size(); ++iToDrop)
		{
			//find the one to remove
			for (std::vector<CGoNode*>::iterator iter = m_connections.begin( ); iter != m_connections.end( ); iter++)
			{
				if ((*iter) == connetionsToDrop[iToDrop].NodeToDrop)
				{
					m_connections.erase(iter);
					break;
				}
			}
		}
	}
}

void CGoNode::setState(EState i_state)
{
	switch(m_state)
	{
	case EState_Constructing:
		{
			SetAlpha(1.0f);
		}
		break;
	case EState_Placing:
		{
			calculateConnections();
			
			//let all the other gos know about this connection 
			for(int iConnection = 0; iConnection < m_connections.size(); ++iConnection)
			{
				m_connections[iConnection]->OnConnectionGained(this);
			}
		}
		break;
	}
	
	m_state = i_state;
	m_tState = T();
}

bool CGoNode::HasConnectionSlot() const
{
	return DoesProvideConnection() && (m_connectionsAllowed > m_connections.size());
}

void CGoNode::VisitNodesChildren()
{
	for(int iConnection = 0; iConnection < m_connections.size(); ++iConnection)
	{
		if (m_connections[iConnection]->IsConstructed() && !m_connections[iConnection]->HasNodeBeenVisited())
		{
			m_connections[iConnection]->VisitNode();
			m_connections[iConnection]->VisitNodesChildren();
		}
	}
}

void CGoNode::removingFromNodeList()
{
	//tell all my connections that this connections was severed
	for(int iConnection = 0; iConnection < m_connections.size(); ++iConnection)
	{
		m_connections[iConnection]->OnConnectionSevered(this);
	}
	
	//remove all my connections
	m_connections.clear();
}

void CGoNode::OnConnectionSevered(const CGoNode* i_severedNode)
{
	//sanity check
	if (m_connections.size() == 0)
	{
		printf("ERROR: Node contacted for connection severed, but has none.... why!?\n");
		return;
	}
	
	//find the connection
	for (std::vector<CGoNode*>::iterator iter = m_connections.begin( ); iter != m_connections.end( ); iter++)
	{
		if ((*iter) == i_severedNode)
		{
			m_connections.erase(iter);
			return;
		}
	}
	
	printf("ERROR: Node contacted for connection severed, but node wasn't found!!\n");
}

void CGoNode::OnConnectionGained(CGoNode* i_nodeGained)
{
	//sanity check.. make sure this node isn't already in the list
	for(int iConnection = 0; iConnection < m_connections.size(); ++iConnection)
	{
		if (m_connections[iConnection] == i_nodeGained)
		{
			printf("ERROR: Node contacted for connection gain, but is already in the list");
			return;
		}
	}
	
	m_connections.push_back(i_nodeGained);
}

/*--------------------------------------------------------------------------*/

CGoMinerals::CGoMinerals()
{
	m_mineralCount = 300.0f;
	
	setStateFromMineralCount();
    
    
}

void CGoMinerals::Update()
{
	super::Update();
	
	setStateFromMineralCount();
}

void CGoMinerals::setStateFromMineralCount()
{
	if (m_mineralCount < 100.0f)
	{
		setState(EState_Small);
	}
	else if (m_mineralCount < 250.0f)
	{
		setState(EState_Medium);
	}
	else 
	{
		setState(EState_Large);
	}
}

void CGoMinerals::setState(EState i_state)
{
	//early out
	if (m_state == i_state)
	{
		return;
	}
	
	m_state = i_state;
	
	switch(m_state)
	{
	case EState_Small:
		{
			SetTexture("minerals_small.png");
			SetBoundingRadius(0.05f);
		}
		break;
	case EState_Medium:
		{
			SetTexture("minerals_medium.png");
			SetBoundingRadius(0.1f);
		}
		break;
	case EState_Large:
		{
			SetTexture("minerals_large.png");
			SetBoundingRadius(0.2f);
		}
		break;
	}
}

float CGoMinerals::Mine(float i_amount)
{
	if (IsEmpty())
	{
		return 0;
	}
	
	i_amount = BBMIN(i_amount, m_mineralCount);
	m_mineralCount -= i_amount;
	
	if (m_mineralCount == 0)
	{
		setState(EState_Empty);
		RemoveFromWorld();
	}
	
	 //add the minerals to the player
	 g_player.AddMinerals(i_amount);
	
	return i_amount;
}

/*-----------------------------------EOF!-----------------------------------*/ 